"""
内容：输入数值二元分类模型示例
日期：2020年7月6日
作者：Howie
"""
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.utils import plot_model
import matplotlib.pyplot as plt
import pandas as pd

# 预设
N_SAMPLES_TRAIN = 1000
N_SAMPLES_TEST = 100
N_FEATURES = 12
EPOCH = 1000
BATCH_SIZE = 64
N_CLASSES = 2


def hist_plot(hist, model_name):
    """
    # 可视化训练过程
    :param hist: history对象
    :return:
    """
    fig, loss_ax = plt.subplots()
    acc_ax = loss_ax.twinx()
    # 每个训练周期的训练误差与验证误差
    loss_ax.plot(hist.history['loss'], 'y', label='Loss')
    # 每个训练周期的训练精度与验证精度
    acc_ax.plot(hist.history['accuracy'], 'b', label='Acc')
    # 横轴与纵轴
    loss_ax.set_xlabel('epoch')
    loss_ax.set_ylabel('loss')
    acc_ax.set_ylabel('accuracy')
    # 标签
    loss_ax.legend(loc='upper left')
    acc_ax.legend(loc='lower left')
    # 标题
    plt.title(model_name)
    # 保存
    plt.savefig('./logs/History_Demo2_' + model_name + '.pdf')
    # 展示
    plt.show()


def dataset_preparation(verbose=False):
    """
    # 准备数据集
    :return:
    """
    # 二元分类模型
    X_train = np.random.random(
        (N_SAMPLES_TRAIN, N_FEATURES))  # 1000个用于训练的具有12个任意值的输入值
    Y_train = np.random.randint(N_CLASSES, size=(N_SAMPLES_TRAIN, 1))
    X_test = np.random.random((N_SAMPLES_TEST, N_FEATURES))
    Y_test = np.random.randint(N_CLASSES, size=(N_SAMPLES_TEST, 1))

    if verbose:
        print(
            "Train Samples: {}, Test Samples: {}".format(
                X_train.shape,
                X_test.shape))
        plt.figure(0)
        plt.title('Samples Visualization')
        bar_width = 0.1  # 设置柱与柱之间的宽度
        rects = plt.bar(np.arange(2), height=list(
            pd.value_counts([label for label in Y_train])), width=0.1)
        for rect in rects:
            height = rect.get_height()
            plt.text(
                rect.get_x() +
                rect.get_width() /
                2,
                height,
                str(height),
                ha='center',
                va='bottom')
        rects = plt.bar(np.arange(2) + bar_width,
                        height=list(pd.value_counts([label for label in Y_test])),
                        width=0.1)
        for rect in rects:
            height = rect.get_height()
            plt.text(
                rect.get_x() +
                rect.get_width() /
                2,
                height,
                str(height),
                ha='center',
                va='bottom')
        plt.xticks(np.arange(2), ['Neg', 'Pos'])
        plt.legend(
            loc='upper center', bbox_to_anchor=(
                0.5, -0.03), fancybox=True, ncol=5)
        plt.savefig('./logs/Samples_Demo2.pdf')

        plt.figure(1)
        plot_x = X_train[:, 0]
        plot_y = X_train[:, 1]
        plot_color = Y_train.reshape(1000, )
        plt.scatter(plot_x, plot_y, c=plot_color)
        plt.xlabel('Components 1')
        plt.ylabel('Components 2')
        plt.savefig('./logs/Components_2D.pdf')

        fig = plt.figure(2)
        ax = fig.add_subplot(111, projection='3d')

        plot_x = X_train[:, 0]
        plot_y = X_train[:, 1]
        plot_z = X_train[:, 2]
        plot_color = Y_train.reshape(1000, )
        ax.set_xlabel('Components 1')
        ax.set_ylabel('Components 2')
        ax.set_zlabel('Components 3')
        ax.scatter(plot_x, plot_y, plot_z, c=plot_color)
        plt.savefig('./logs/Components_3D.pdf')

        plt.show()

    return X_train, X_test, Y_train, Y_test


def model_building(choice='Multi-Layer Perceptron'):
    model = Sequential()
    if choice == 'Multi-Layer Perceptron':
        model.add(Dense(units=64, input_dim=12))
        model.add(Activation('relu'))
        model.add(Dense(units=1))
        # sigmoid激活函数，映射输入值返回0~1的值。输出值大于特定阈值（如0.5）为阳性，小于
        # 为阴性，对输入值进行预测，通常用于二元分类问题的输出层
        model.add(Activation('sigmoid'))
    elif choice == 'Deep Multi-Layer Perceptron':
        model.add(Dense(units=64, input_dim=12))
        model.add(Activation('relu'))
        model.add(Dense(units=64))
        model.add(Activation('relu'))
        model.add(Dense(units=1))
        model.add(Activation('sigmoid'))
    else:
        model.add(Dense(units=1, input_dim=12))
        model.add(Activation('sigmoid'))

    plot_model(
        model,
        to_file='./logs/Demo2_model_' +
        choice +
        '.pdf',
        show_shapes=True)

    return model


def model_training():
    # 生成数据集
    X_train, X_test, Y_train, Y_test = dataset_preparation()
    titles = ['Multi-Layer Perceptron',
              'Deep Multi-Layer Perceptron',
              'Perceptron']
    for title in titles:
        # 搭建模型
        model = model_building(choice=title)
        model.compile(
            optimizer='rmsprop',
            loss='binary_crossentropy',
            metrics=['accuracy'])
        # 训练模型
        hist = model.fit(X_train, Y_train, epochs=EPOCH, batch_size=BATCH_SIZE)
        # 查看训练过程
        hist_plot(hist, model_name=title)
        # 评价模型
        loss_and_metrics = model.evaluate(
            X_test, Y_test, batch_size=BATCH_SIZE // 2)
        print("Loss and metrics: {}".format(loss_and_metrics))


if __name__ == '__main__':
    model_training()
